using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// Quintic bezier.
/// </summary>
[System.Serializable]
public class PathQuinticBezier : IPath
{
	/// <summary>
	/// Create a quintic bezier curve from startAnchor to endAnchor,
	/// resulting in a straight line with four anchors along this line.
	/// </summary>
	public PathQuinticBezier (Vector3 startAnchor, Vector3 endAnchor)
	{
		m_anchors = new List<Vector3> ();
		m_sampledPoints = new List<Vector3> ();
		m_sampledLenAcc = new List<float> ();
		
		Vector3 distance = endAnchor - startAnchor;
		m_anchors.Add (startAnchor);
		m_anchors.Add (startAnchor + distance / 5.0f);
		m_anchors.Add (startAnchor + distance * 2.0f / 5.0f);
		m_anchors.Add (startAnchor + distance * 3.0f / 5.0f);
		m_anchors.Add (startAnchor + distance * 4.0f / 5.0f);
		m_anchors.Add (endAnchor);
		
		Sample ();
	}
	
	/// <summary>
	/// Change a specified anchor to new position.
	/// </summary>
	public void SetAnchor (int anchorIndex, Vector3 anchor)
	{
		m_anchors[anchorIndex] = anchor;
	}
	
	/// <summary>
	/// Gets an anchor of this curve
	public Vector3 GetAnchor (int anchorIndex)
	{
		return m_anchors[anchorIndex];
	}
	
	/// <summary>
	/// Sample this curve with successive lines
	/// </summary>
	public void Sample (int sampleCount = 100)
	{
		m_sampledLenAcc.Clear ();
		m_sampledPoints.Clear ();
		m_sampledLength = 0;
		
		Vector3 preXZPoint = Vector3.zero;
		float t = 0, step = 1.0f / sampleCount;
		for (int i = 0; i <= sampleCount; ++ i, t += step)
		{
			Vector3 samplePoint = Solve (t);
			m_sampledPoints.Add (samplePoint);
			
			Vector3 sampleXZPoint = new Vector3 (samplePoint.x, 0, samplePoint.z);
			if (i != 0)
			{
				m_sampledLength += Vector3.Distance (sampleXZPoint, preXZPoint);
			}
			preXZPoint = sampleXZPoint;
			
			m_sampledLenAcc.Add (m_sampledLength);
		}
	}
	
	/// <summary>
	/// Gets a point on this curve, of which y compnent is always zero.
	/// The returned point will not be exactly on this curve for most of
	/// the time, but between two sampled points of this curve, S1 and S2,
	/// where sampled length of this curve before S1 is samller than length
	/// and sampled length of this curve before S2 is larger than length.
	/// </summary>
	public Vector3 GetXZPointOnCurve (float length, out Vector3 tagentXZ)
	{
		if (length > m_sampledLength)
		{
			int index = m_sampledPoints.Count - 1;
			tagentXZ = m_sampledPoints[index] - m_sampledPoints[index - 1];
			tagentXZ.y = 0;
			Vector3 p = m_sampledPoints[index];
			p.y = 0;
			return p;
		}
		
		for (int i = 0, len = m_sampledLenAcc.Count; i < len; ++ i)
		{
			if (m_sampledLenAcc[i] >= length)
			{
				if (i != 0)
				{
					float sectionLen = m_sampledLenAcc[i] - m_sampledLenAcc[i - 1];
					float targetLen = length - m_sampledLenAcc[i - 1];
					Vector3 sectionV = m_sampledPoints[i] - m_sampledPoints[i - 1];
					sectionV.y = 0;
					Vector3 preSamplePoint = m_sampledPoints[i - 1];
					preSamplePoint.y = 0;
					tagentXZ = sectionV;
					return preSamplePoint + sectionV * targetLen / sectionLen;
				}
				else
				{
					Vector3 p = m_sampledPoints[0];
					p.y = 0;
					tagentXZ = m_sampledPoints[1] - m_sampledPoints[0];
					tagentXZ.y = 0;
					return p;
				}
			}
		}
		tagentXZ = Vector3.zero;
		return Vector3.zero;
	}
	
	/// <summary>
	/// Get point on the curve with t, which should be between 0 and 1.
	/// </summary>
	public Vector3 Solve (float t)
	{
		t = Mathf.Clamp01 (t);
		
		float oneMinusT = 1.0f - t;
		return m_anchors[0] * Mathf.Pow (oneMinusT, 5)
			 + 5 * m_anchors[1] * t * Mathf.Pow (oneMinusT, 4)
			 + 10 * m_anchors[2] * t * t * Mathf.Pow (oneMinusT, 3)
			 + 10 * m_anchors[3] * Mathf.Pow (t, 3) * oneMinusT * oneMinusT
			 + 5 * m_anchors[4] * Mathf.Pow (t, 4) * oneMinusT
			 + m_anchors[5] * Mathf.Pow (t, 5);
	}
	
	/// <summary>
	/// Render this curve in editor mode. Call this method in OnDrawGizmos.
	/// </summary>
	public void Render ()
	{
		// draw sampled lines
		Color rawColor = Gizmos.color;
		Gizmos.color = Color.green;
		for (int i = 0, len = m_sampledPoints.Count; i < len - 1; ++ i)
		{
			Gizmos.DrawLine (m_sampledPoints[i], m_sampledPoints[i + 1]);
			/*
			if (i != 0)
			{
				Gizmos.color = Color.yellow;
				Gizmos.DrawSphere (m_sampledPoints[i], 0.15f);
				Gizmos.color = Color.green;
			}
			*/
		}
		
		// draw anchor points
		/*
		Gizmos.color = Color.red;
		for (int i = 0, len = m_anchors.Count; i < len; ++ i)
		{
			Gizmos.DrawSphere (m_anchors[i], 0.5f);
			
			if (i < len - 1)
			{
				Gizmos.color = Color.gray;
				Gizmos.DrawLine (m_anchors[i], m_anchors[i + 1]);
				Gizmos.color = Color.red;
			}
			
		}
		*/
		
		Gizmos.color = rawColor;
	}
	
	/// <summary>
	/// Gets the number of anchors of this curve.
	/// </summary>
	public int anchorCount
	{
		get { return m_anchors.Count; }
	}
	
	/// <summary>
	/// Gets the length of this curve after sampling.
	/// </summary>
	public float sampledLength
	{
		get { return m_sampledLength; }
	}
	
	/// <summary>
	/// Inversed path shares the same anchor with this one, but inversed
	/// anchor order.
	/// </summary>
	public IPath inversedPath 
	{ 
		get
		{
			if (m_inversedPath == null)
			{
				m_inversedPath = new PathQuinticBezier (m_anchors[5], m_anchors[0]);
				m_inversedPath.Sample (m_sampledPoints.Count);
			}
			return m_inversedPath;
		}
		
		set
		{
			m_inversedPath = value;
		}
	}
	
	/// <summary>
	/// anchors of this curve.
	/// </summary>
	[SerializeField] List<Vector3> m_anchors;
	
	/// <summary>
	/// sampled points of this curve.
	/// </summary>
	[SerializeField] List<Vector3> m_sampledPoints;
	
	/// <summary>
	/// length accumulated for each sampled points.
	/// </summary>
	[SerializeField] List<float> m_sampledLenAcc;
	
	/// <summary>
	/// length of this curve after sampling.
	/// </summary>
	[SerializeField] float m_sampledLength;
	
	/// <summary>
	/// inversed path of this one.
	/// </summary>
	[SerializeField] IPath m_inversedPath = null;
}
